﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;

namespace sendfile_server
{
    public class SFServerConnection : SFConnection
    {
        // all connections connected to this server
        public static Hashtable m_AllConnections = new Hashtable();
        private const int DEFAULT_LENGTH_BYTES = 4;

        //---------------------------------------------------------------------
        // events  
        //---------------------------------------------------------------------
        public event EventHandler OnConnected;

        //---------------------------------------------------------------------
        //  
        //---------------------------------------------------------------------
        public SFServerConnection()
        {

        }

        /// <summary>
        ///  construct in server
        /// </summary>
        public SFServerConnection(TcpClient _client)
        {
            m_TcpClient = _client;
            _client.ReceiveBufferSize = 256 * 1024;

            if (m_TcpClient.Connected)
            {
                if (OnConnected!=null)
                    OnConnected(this, EventArgs.Empty);
                
                TraceMsg("Accept Msg from " + m_TcpClient.Client.LocalEndPoint.ToString());
                m_IsConnected = true;
                m_ClientIP = (IPEndPoint)_client.Client.RemoteEndPoint;
                lock(m_AllConnections)
                {
                    m_AllConnections.Add(m_ClientIP.ToString(), this);
                }

                // read length field 
                ClientState cs = new ClientState(GetStream(), new byte[DEFAULT_LENGTH_BYTES]);
                IAsyncResult result = GetStream().BeginRead(
                        cs.ByteBuffer, cs.TotalBytes, cs.ByteBuffer.Length - cs.TotalBytes,
                        new AsyncCallback(ReceivedLengthCallback), cs);
            }
        }

        /// <summary>
        /// header: length + type 
        /// </summary>
        private void ReceivedLengthCallback(IAsyncResult iar)
        {
            try
            {
                ClientState cs = (ClientState)iar.AsyncState;
                int bytesRead = GetStream().EndRead(iar);
                cs.AddToTotalBytes(bytesRead);

                // if client has disconnected 
                if (bytesRead < 1)
                {
                    lock(m_AllConnections) {
                        m_AllConnections.Remove(m_TcpClient.ToString());
                    }
                    TraceMsg("Disconnect " + m_TcpClient.Client.LocalEndPoint.ToString());
                    return;
                }

                // recevie 4 byte length completed, expand msg buffer size
                if (cs.TotalBytes == cs.ByteBuffer.Length)
                {
                    int msg_len = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(cs.ByteBuffer, 0));
                    // expand byte buffer 
                    byte[] msg_buff = new byte[msg_len + DEFAULT_LENGTH_BYTES];
                    Array.Copy(cs.ByteBuffer, msg_buff, bytesRead);
                    cs.ByteBuffer = msg_buff;
                }

                IAsyncResult result = cs.NetStream.BeginRead(
                            cs.ByteBuffer, cs.TotalBytes, cs.ByteBuffer.Length - cs.TotalBytes,
                            new AsyncCallback(ReceivedBodyCallback), cs);

            }
            catch (Exception ex)
            {
                lock (m_AllConnections)
                {
                    m_AllConnections.Remove(m_TcpClient.ToString());
                }
                TraceMsg("Disconnect " + m_TcpClient.Client.RemoteEndPoint.ToString() + 
                    "due to " + ex.Message);
                return;
            }
        }

        /// <summary>
        /// receive body
        /// </summary>
        private void ReceivedBodyCallback(IAsyncResult iar)
        {
            try
            {


                ClientState cs = (ClientState)iar.AsyncState;
                int bytesRead;

                bytesRead = cs.NetStream.EndRead(iar);
                cs.AddToTotalBytes(bytesRead);
                TraceMsg("Receive bytes: " + bytesRead);

                // if client has disconnected
                if (bytesRead < 1)
                {
                    lock (m_AllConnections)
                    {
                        m_AllConnections.Remove(m_TcpClient.ToString());
                        TraceMsg("Disconnect " + m_TcpClient.ToString());
                    }
                    return;
                }

                // receive body completed
                if (cs.TotalBytes == cs.ByteBuffer.Length)
                {
                    SFMessage msg = DecodeMessage(cs.ByteBuffer, 0, cs.ByteBuffer.Length);
                    TraceMsg("Receive Message" + Enum.GetName(typeof(MessageTypeEnum), msg.MessageType));
                }

                // if not receive completed
                else if (cs.TotalBytes < cs.ByteBuffer.Length)
                {
                    TraceMsg("need to receive more bytes");
                    cs.NetStream.BeginRead(cs.ByteBuffer, cs.TotalBytes,
                        cs.ByteBuffer.Length - cs.TotalBytes,
                        new AsyncCallback(ReceivedBodyCallback), cs);
                }

                // continue reading next header from client
                cs = new ClientState(GetStream(), new byte[DEFAULT_LENGTH_BYTES]);
                IAsyncResult result = GetStream().BeginRead(
                        cs.ByteBuffer, cs.TotalBytes, cs.ByteBuffer.Length - cs.TotalBytes,
                        new AsyncCallback(ReceivedLengthCallback), cs);
            }
            catch (Exception ex)
            {
                lock (m_AllConnections)
                {
                    m_AllConnections.Remove(m_TcpClient.ToString());
                    TraceMsg("Disconnect " + m_TcpClient.Client.RemoteEndPoint.ToString() +
                        "due to " + ex.Message);

                }
                return;
            }
        }

        private SFMessage DecodeMessage(byte[] msgbuff, int offset, int len)
        {
            int msg_type = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(msgbuff, 4));
            SFMessage msg = null;
            switch (msg_type)
            {
                case (int)MessageTypeEnum.PING:
                    msg = PingMessage.Parse(msgbuff);
                    OnPing(msg as PingMessage);
                    break;
                case (int)MessageTypeEnum.FILEDATE:
                    msg = FileDataMessage.Parse(msgbuff);
                    if (msg != null)
                    {
                        //fmsg.Dump();
                        OnFileDataReceive(msg as FileDataMessage);
                    }
                    break;
                defalut:
                    break;
            }
            return msg;
        }

        private void OnPing(PingMessage msg)
        {
            TraceMsg("receiving ping msg, send pong msg");
            PongMessage pong = new PongMessage();
            SendMsg(pong);
        }

        private void OnFileDataReceive(FileDataMessage msg)
        {
            TraceMsg("handle filedata msg");
            try
            {
                // 3) read file data and save to local file system
                FileStream fs = new FileStream(msg.FileName + ".new", FileMode.Create, FileAccess.Write);
                if (fs.CanWrite)
                {
                    fs.Write(msg.FileData, 0, msg.FileData.Length);
                }
                fs.Close();
                string x = String.Format("Write data to file : {0} with size {1} Successfully!",
                    msg.FileName, msg.FileData.Length);
                TraceMsg(x);


            }
            catch (Exception ex)
            {
                TraceMsg(ex.Message);
                throw ex;
            }
        }

        public void TraceMsg(string msg)
        {
            Console.WriteLine("[{0,3}] - {1} : {2}",
                    Thread.CurrentThread.ManagedThreadId,
                    DateTime.Now.ToString("HH:mm:ss.ffff"), msg);
        }
    }

    class sfserverConn_test
    {
        // start a client to test this sample to show incoming endpoint 
        // i.e. 
        // netcat.exe localhost 8080 "test"
        // from vm
        // use: netcase 192.168.44.2 8080
        public static void TestSimple()
        {
            TraceMsg("Starting Server on Port 8080");
            TraceMsg("Start a client to test it");

            // TcpListener listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 8080);
            TcpListener listener = new TcpListener(IPAddress.Loopback, 8080);
            listener.ExclusiveAddressUse = false;
            listener.Start();
            TraceMsg("Listening on port 8080");

            while (true)
            {
                TcpClient clientB = listener.AcceptTcpClient();
                SFServerConnection con = new SFServerConnection(clientB);
                TraceMsg(con.ClientIP.ToString());

            }
        }

        public static void TraceMsg(string msg)
        {
            Console.WriteLine("[{0,3}] - {1} : {2}",
                    Thread.CurrentThread.ManagedThreadId,
                    DateTime.Now.ToString("HH:mm:ss.ffff"), msg);
        }

        public static void TestByThreading()
        {
            StartServerThread();

            StartClientThread();
        }

        /// <summary>
        /// start server thread, for every connection, start read thread to read message
        /// </summary>
        private static void StartServerThread()
        {
            Thread serverThread = new Thread(delegate()
            {
                TcpListener listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 8080);
                listener.ExclusiveAddressUse = false;
                listener.Start();
                TraceMsg("Listening on port 8080");

                while (true)
                {
                    TcpClient clientB = listener.AcceptTcpClient();
                    SFServerConnection con = new SFServerConnection(clientB);
                }
            });
            serverThread.Start();
        }

        /// <summary>
        /// start client thread to connect the server thread
        /// </summary>
        private static void StartClientThread()
        {
            Thread t1 = new Thread(delegate()
            {
                SFClientConnection con = new SFClientConnection();
                con.Connect("127.0.0.1", 8080);
                TraceMsg("Connection to server");


                NetworkStream ns = con.GetStream();

                // send basic message
                //for (int i = 0; i < 25; i++)
                //{

                //    SFMessage smsg = new PingMessage();
                //    //smsg.Dump();

                //    ns.Write(smsg.GetBytes(), 0, smsg.GetBytes().Length);
                //    TraceMsg("send basic message");
                //}

                // send FileDataMessage
                for (int i = 0; i < 2; i++)
                {
                    byte[] filedata = new byte[] { 0xff, 0xff, 0xff, 0xff };
                    SFMessage fmsg3 = new FileDataMessage("phone.txt", filedata);
                    //fmsg3.Dump();
                    ns.Write(fmsg3.GetBytes(), 0, fmsg3.GetBytes().Length);
                    Thread.Sleep(300);
                    TraceMsg("send filedata");
                }

            });
            t1.Start();

        }

    }
}
